package com.such.kit.ftp;

import java.io.InputStream;
import java.io.OutputStream;
import java.io.Reader;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.Properties;
import java.util.Vector;
import org.apache.tools.ant.util.ReaderInputStream;
import org.apache.tools.zip.ZipEntry;
import org.apache.tools.zip.ZipOutputStream;
import com.jcraft.jsch.ChannelSftp;
import com.jcraft.jsch.JSch;
import com.jcraft.jsch.JSchException;
import com.jcraft.jsch.Session;
import com.jcraft.jsch.SftpATTRS;
import com.jcraft.jsch.SftpException;
import com.such.kit.Callable;
import com.such.kit.Logger;
import com.such.kit.Retry;
import com.such.kit.file.FileCommons;
import com.such.kit.file.bean.FileInfo;
import com.such.kit.file.bean.FileOrder;
import com.such.kit.ftp.support.Ftp;
import com.such.kit.stream.StreamCommons;
import com.such.kit.validate.ValidateCommons;

public class SftpCommons extends Ftp {

	private static String channelTypeSftp = "sftp";

	private Session session;
	private ChannelSftp channelSftp;
	private String ftpHome;
	private String prvkey;
	private String passphrase;

	/**
	 * <pre>
	 * 设置 FTP 额外参数：
	 * prvkey [参考 {@link JSch#addIdentity(String, String)}]
	 * passphrase [参考 {@link JSch#addIdentity(String, String)}]
	 * </pre>
	 * @see com.such.kit.ftp.support.Ftp#setProperty(java.lang.String, java.lang.Object)
	 */
	public void setProperty(String key, Object value) {
		if ("prvkey".equals(key)) {
			this.prvkey = (String) value;
		} else if ("passphrase".equals(key)) {
			this.passphrase = (String) value;
		}
	}

	public boolean connect(final String ip, final int port, final String username, final String password) throws Exception {
		new Retry<String, Boolean>() {
			public Boolean getObject(String param) throws Exception {
				Properties config = new Properties();
				config.put("StrictHostKeyChecking", "no");

				JSch jsch = new JSch();
				if (ValidateCommons.isEmpty(password)) {
					try {
						jsch.addIdentity(prvkey, passphrase);
					} catch (JSchException e) {
						Logger.warn(getClass(), "创建私钥失败", e);
					}
				}
				session = jsch.getSession(username, ip, port);
				if (ValidateCommons.isNotEmpty(password)) {
					session.setPassword(password);
				}
				session.setConfig(config);
				session.connect();

				channelSftp = (ChannelSftp) session.openChannel(channelTypeSftp);
				channelSftp.connect();

				return true;
			}
		}.doRetry();
		if (ValidateCommons.isEmpty(this.session) || ValidateCommons.isEmpty(this.channelSftp)) {
			return false;
		} else {
			this.ftpHome = FileCommons.changeCompatSeparator(this.channelSftp.getHome());
			return true;
		}
	}

	public void logout() throws Exception {
		if (ValidateCommons.isNotEmpty(this.channelSftp) && this.channelSftp.isConnected()) {
			this.channelSftp.exit();
			this.channelSftp.disconnect();
		}
		if (ValidateCommons.isNotEmpty(this.session) && this.session.isConnected()) {
			this.session.disconnect();
		}
	}

	public boolean rename(String ftpPath, String newName) throws Exception {
		final String oldPath = FileCommons.appendPathNode(this.ftpHome, ftpPath);
		final String newPath = FileCommons.appendPathNode(this.ftpHome, FileCommons.cutLastPathNode(ftpPath), newName);
		deleteFile(newPath);
		return new Retry<String, Boolean>() {
			public Boolean getObject(String param) throws Exception {
				try {
					channelSftp.rename(encodeURI(oldPath), encodeURI(newPath));
				} catch (SftpException e) {
					Logger.warn(getClass(), "sftp 远程重命名失败", e);
					return false;
				}
				return true;
			}
		}.doRetry();
	}

	public void createFolder(String ftpPath) throws Exception {
		String path = FileCommons.cutLastPathNode(ftpPath);
		if (ValidateCommons.isNotEmpty(path)) {
			createFolder(encodeURI(path));
		}
		new Retry<String, Boolean>(ftpPath) {
			public Boolean getObject(String ftpPath) throws Exception {
				String path = encodeURI(FileCommons.appendPathNode(ftpHome, ftpPath));
				try {
					channelSftp.cd(path);
					channelSftp.cd(ftpHome);
				} catch (SftpException e) {
					channelSftp.mkdir(path);
				}
				return true;
			}
		}.doRetry();
	}

	public void deleteFolder(String ftpPath) throws Exception {
		ftpPath = FileCommons.changeCompatSeparator(ftpPath);
		deleteFiles(ftpPath, true);
		new Retry<String, Boolean>(ftpPath) {
			public Boolean getObject(String ftpPath) throws Exception {
				channelSftp.rmdir(encodeURI(FileCommons.appendPathNode(ftpHome, ftpPath)));
				return true;
			}
		}.doRetry();
	}

	public void downloadFolder(String ftpPath, ZipOutputStream out) throws Exception {
		downloadFolder(ftpPath, "", out);
	}

	public void downloadFolder(String ftpPath, String zipParentPath, ZipOutputStream out) throws Exception {
		downloadFiles(listFolder(ftpPath, FileOrder.ORDER_MODE_NAME, false), zipParentPath, out);
	}

	public List<FileInfo> listFolder(String ftpPath, final String orderMode, final boolean isDesc) throws Exception {
		List<FileInfo> fileInfoList = new ArrayList<FileInfo>();
		if (ValidateCommons.isNotEmpty(ftpPath)) {
			ftpPath = FileCommons.changeCompatSeparator(ftpPath);
			fileInfoList = new Retry<String, List<FileInfo>>(ftpPath) {
				public List<FileInfo> getObject(String ftpPath) throws Exception {
					List<FileInfo> fileInfoList = new ArrayList<FileInfo>();
					Vector<ChannelSftp.LsEntry> files = channelSftp.ls(encodeURI(FileCommons.appendPathNode(ftpHome, ftpPath)));
					if (ValidateCommons.isNotEmpty(files)) {
						for (ChannelSftp.LsEntry file : files) {
							String name = new String(file.getFilename().getBytes(ftpCharset), localCharset);
							if (!".".equals(name) && !"..".equals(name)) {
								String path = FileCommons.appendPathNode(ftpPath, name);
								SftpATTRS attrs = file.getAttrs();
								FileInfo fb = new FileInfo(path, attrs.getSize(), new Date(attrs.getMTime()));
								fb.setAttribute(attrs.isDir(), false, true, false, false);
								if (ValidateCommons.isEmpty(fileFilter) || fileFilter.accept(fb)) {
									fileInfoList.add(fb);
								}
							}
						}
						Collections.sort(fileInfoList, new FileOrder(orderMode, isDesc));
					}
					return fileInfoList;
				}
			}.doRetry();
		}
		return fileInfoList;
	}

	public boolean isFolder(String ftpPath) throws Exception {
		if (ValidateCommons.isEmpty(ftpPath)) {
			throw new Exception("指定的路径为空");
		}
		List<FileInfo> fileInfoList = listFolder(ftpPath, FileOrder.ORDER_MODE_NAME, false);
		if (ValidateCommons.isNotEmpty(fileInfoList)) {
			for (FileInfo fb : fileInfoList) {
				if (fb.getName().equals(FileCommons.getLastPathNode(ftpPath))) {
					if (fb.isDirectory()) {
						return true;
					} else {
						break;
					}
				}
			}
		}
		return false;
	}

	public boolean createFile(final InputStream localIn, String ftpPath) throws Exception {
		ftpPath = FileCommons.changeCompatSeparator(ftpPath);
		String path = FileCommons.cutLastPathNode(ftpPath);
		if (ValidateCommons.isNotEmpty(path)) {
			createFolder(encodeURI(path));
		}
		boolean boole = false;
		try {
			boole = new Retry<String, Boolean>(ftpPath) {
				public Boolean getObject(String ftpPath) throws Exception {
					try {
						channelSftp.put(localIn, encodeURI(FileCommons.appendPathNode(ftpHome, ftpPath)));
					} catch (SftpException e) {
						Logger.warn(getClass(), "sftp 远程创建文件失败", e);
						return false;
					}
					return true;
				}
			}.doRetry();
		} finally {
			StreamCommons.closePure(localIn);
		}
		return boole;
	}

	public boolean createFile(Reader localR, String ftpPath) throws Exception {
		return createFile(new ReaderInputStream(localR), ftpPath);
	}

	public boolean deleteFile(String ftpPath) throws Exception {
		ftpPath = FileCommons.changeCompatSeparator(ftpPath);
		return new Retry<String, Boolean>(ftpPath) {
			public Boolean getObject(String ftpPath) throws Exception {
				try {
					channelSftp.rm(encodeURI(FileCommons.appendPathNode(ftpHome, ftpPath)));
				} catch (SftpException e) {
					Logger.warn(getClass(), "sftp 远程删除文件失败", e);
					return false;
				}
				return true;
			}
		}.doRetry();
	}

	public void deleteFiles(String ftpPath, boolean containFolder) throws Exception {
		deleteFiles(listFolder(ftpPath, FileOrder.ORDER_MODE_NAME, false), containFolder);
	}

	public void deleteFiles(List<FileInfo> fileInfoList, boolean containFolder) throws Exception {
		if (ValidateCommons.isNotEmpty(fileInfoList)) {
			for (FileInfo fb : fileInfoList) {
				String ftpPath = fb.getPath();
				if (fb.isDirectory() && containFolder) {
					deleteFolder(ftpPath);
				} else {
					deleteFile(ftpPath);
				}
			}
		}
	}

	public <T> T downloadFile(String ftpPath, final Callable<InputStream, T> callable) throws Exception {
		return new Retry<String, T>(ftpPath) {
			public T getObject(String ftpPath) throws Exception {
				T returnObj = null;
				InputStream in = channelSftp.get(encodeURI(FileCommons.appendPathNode(ftpHome, ftpPath)));
				try {
					returnObj = callable.call(in);
				} finally {
					StreamCommons.close(in);
				}
				return returnObj;
			}
		}.doRetry();
	}

	public boolean downloadFile(String ftpPath, final OutputStream out) throws Exception {
		return new Retry<String, Boolean>(ftpPath) {
			public Boolean getObject(String ftpPath) throws Exception {
				try {
					channelSftp.get(encodeURI(FileCommons.appendPathNode(ftpHome, ftpPath)), out);
				} catch (SftpException e) {
					Logger.warn(getClass(), "sftp 远程文件下载失败", e);
					return false;
				}
				return true;
			}
		}.doRetry();
	}

	public void downloadFiles(List<FileInfo> fileInfoList, ZipOutputStream out) throws Exception {
		downloadFiles(fileInfoList, "", out);
	}

	public void downloadFiles(List<FileInfo> fileInfoList, String zipParentPath, ZipOutputStream out) throws Exception {
		if (ValidateCommons.isNotEmpty(fileInfoList)) {
			for (FileInfo fb : fileInfoList) {
				String filename = fb.getName();
				if (ValidateCommons.isNotEmpty(zipParentPath)) {
					filename = FileCommons.appendPathNode(zipParentPath, filename);
				}
				String ftpPath = fb.getPath();
				if (fb.isDirectory()) {
					downloadFolder(ftpPath, filename, out);
				} else {
					ZipEntry entry = new ZipEntry(filename);
					out.putNextEntry(entry);
					downloadFile(ftpPath, out);
				}
			}
		}
	}

	public boolean transferFile(String ftpPath, final Ftp targetFtp, final String targetPath) throws Exception {
		return new Retry<String, Boolean>(ftpPath) {
			public Boolean getObject(String ftpPath) throws Exception {
				boolean boole = false;
				InputStream in = channelSftp.get(encodeURI(FileCommons.appendPathNode(ftpHome, ftpPath)));
				try {
					boole = targetFtp.createFile(in, targetPath);
				} finally {
					StreamCommons.close(in);
				}
				return boole;
			}
		}.doRetry();
	}

}
